<!-- directive:breadcrumb ES6学习笔记 -->
<!-- directive:title ES6学习笔记 -->
<div class='panel-body'>
    <p>本章介绍一些尚未进入标准、但很有希望的最新提案。</p>
    <p>do 表达式</p>
    <p>本质上，块级作用域是一个语句，将多个操作封装在一起，没有返回值。</p>
    <p>{</p>
    <p>    let t = f();</p>
    <p>    t = t * t + 1;</p>
    <p>}</p>
    <p>上面代码中，块级作用域将两个语句封装在一起。但是，在块级作用域以外，没有办法得到t的值，因为块级作用域不返回值，除非t是全局变量。</p>
    <p>现在有一个提案，使得块级作用域可以变为表达式，也就是说可以返回值，办法就是在块级作用域之前加上do，使它变为do表达式，然后就会返回内部最后执行的表达式的值。</p>
    <p>let x = do {</p>
    <p>    let t = f();</p>
    <p>    t * t + 1;</p>
    <p>};</p>
    <p>上面代码中，变量x会得到整个块级作用域的返回值（t * t + 1）。</p>
    <p>do表达式的逻辑非常简单：封装的是什么，就会返回什么。</p>
    <p>// 等同于 <表达式></p>
    <p>do { <表达式>; }</p>
    <p>// 等同于 <语句></p>
    <p>do { <语句> }</p>
    <p>do表达式的好处是可以封装多个语句，让程序更加模块化，就像乐高积木那样一块块拼装起来。</p>
    <p>let x = do {</p>
    <p>    if (foo()) { f() }</p>
    <p>    else if (bar()) { g() }</p>
    <p>    else { h() }</p>
    <p>};</p>
    <p>上面代码的本质，就是根据函数foo的执行结果，调用不同的函数，将返回结果赋给变量x。使用do表达式，就将这个操作的意图表达得非常简洁清晰。而且，do块级作用域提供了单独的作用域，内部操作可以与全局作用域隔绝。</p>
    <p>值得一提的是，do表达式在 JSX 语法中非常好用。</p>
    <p>return (</p>
    <p>    <nav></p>
    <p>        <Home /></p>
    <p>        {</p>
    <p>            do {</p>
    <p>                if (loggedIn) {</p>
    <p>                    <LogoutButton /></p>
    <p>                } else {</p>
    <p>                    <LoginButton /></p>
    <p>                }</p>
    <p>            }</p>
    <p>        }</p>
    <p>    </nav></p>
    <p>)</p>
    <p>上面代码中，如果不用do表达式，就只能用三元判断运算符（?:）。那样的话，一旦判断逻辑复杂，代码就会变得很不易读。</p>
    <p>throw 表达式</p>
    <p>JavaScript 语法规定throw是一个命令，用来抛出错误，不能用于表达式之中。</p>
    <p>// 报错</p>
    <p>console.log(throw new Error());</p>
    <p>上面代码中，console.log的参数必须是一个表达式，如果是一个throw语句就会报错。</p>
    <p>现在有一个提案，允许throw用于表达式。</p>
    <p>// 参数的默认值</p>
    <p>function save(filename = throw new TypeError("Argument required")) {</p>
    <p>}</p>
    <p>// 箭头函数的返回值</p>
    <p>lint(ast, {</p>
    <p>    with: () => throw new Error("avoid using 'with' statements.")</p>
    <p>});</p>
    <p>// 条件表达式</p>
    <p>function getEncoder(encoding) {</p>
    <p>    const encoder = encoding === "utf8" ?</p>
    <p>        new UTF8Encoder() :</p>
    <p>        encoding === "utf16le" ?</p>
    <p>            new UTF16Encoder(false) :</p>
    <p>            encoding === "utf16be" ?</p>
    <p>                new UTF16Encoder(true) :</p>
    <p>                throw new Error("Unsupported encoding");</p>
    <p>}</p>
    <p>// 逻辑表达式</p>
    <p>class Product {</p>
    <p>    get id() {</p>
    <p>        return this._id;</p>
    <p>    }</p>
    <p>    set id(value) {</p>
    <p>        this._id = value || throw new Error("Invalid value");</p>
    <p>    }</p>
    <p>}</p>
    <p>上面代码中，throw都出现在表达式里面。</p>
    <p>语法上，throw表达式里面的throw不再是一个命令，而是一个运算符。为了避免与throw命令混淆，规定throw出现在行首，一律解释为throw语句，而不是throw表达式。</p>
    <p>链判断运算符</p>
    <p>编程实务中，如果读取对象内部的某个属性，往往需要判断一下该对象是否存在。比如，要读取message.body.user.firstName，安全的写法是写成下面这样。</p>
    <p>const firstName = (message</p>
    <p>    && message.body</p>
    <p>    && message.body.user</p>
    <p>    && message.body.user.firstName) || 'default';</p>
    <p>这样的层层判断非常麻烦，因此现在有一个提案，引入了“链判断运算符”（optional chaining operator）?.，简化上面的写法。</p>
    <p>const firstName = message?.body?.user?.firstName || 'default';</p>
    <p>上面代码有三个?.运算符，直接在链式调用的时候判断，左侧的对象是否为null或undefined。如果是的，就不再往下运算，而是返回undefined。</p>
    <p>链判断运算符号有三种用法。</p>
    <p>obj?.prop // 读取对象属性</p>
    <p>obj?.[expr] // 同上</p>
    <p>func?.(...args) // 函数或对象方法的调用</p>
    <p>下面是判断函数是否存在的例子。</p>
    <p>iterator.return?.()</p>
    <p>上面代码中，iterator.return如果有定义，就会调用该方法，否则直接返回undefined。</p>
    <p>下面是更多的例子。</p>
    <p>a?.b</p>
    <p>// 等同于</p>
    <p>a == null ? undefined : a.b</p>
    <p>a?.[x]</p>
    <p>// 等同于</p>
    <p>a == null ? undefined : a[x]</p>
    <p>a?.b()</p>
    <p>// 等同于</p>
    <p>a == null ? undefined : a.b()</p>
    <p>a?.()</p>
    <p>// 等同于</p>
    <p>a == null ? undefined : a()</p>
    <p>使用这个运算符，有几个注意点。</p>
    <p>（1）短路机制</p>
    <p>a?.[++x]</p>
    <p>// 等同于</p>
    <p>a == null ? undefined : a[++x]</p>
    <p>上面代码中，如果a是undefined或null，那么x不会进行递增运算。也就是说，链判断运算符一旦为真，右侧的表达式就不再求值。</p>
    <p>（2）delete 运算符</p>
    <p>delete a?.b</p>
    <p>// 等同于</p>
    <p>a == null ? undefined : delete a.b</p>
    <p>上面代码中，如果a是undefined或null，会直接返回undefined，而不会进行delete运算。</p>
    <p>（3）报错场合</p>
    <p>以下写法是禁止，会报错。</p>
    <p>// 构造函数判断</p>
    <p>new a?.()</p>
    <p>// 运算符右侧是模板字符串</p>
    <p>a?.`{b}`</p>
    <p>// 链判断运算符前后有构造函数或模板字符串</p>
    <p>new a?.b()</p>
    <p>a?.b`{c}`</p>
    <p>// 链运算符用于赋值运算符左侧</p>
    <p>a?.b = c</p>
    <p>（4）右侧不得为十进制数值</p>
    <p>为了保证兼容以前的代码，允许foo?.3:0被解析成foo ? .3 : 0，因此规定如果?.后面紧跟一个十进制数字，那么?.不再被看成是一个完整的运算符，而会按照三元运算符进行处理，也就是说，那个小数点会归属于后面的十进制数字，形成一个小数。</p>
    <p>直接输入 U+2028 和 U+2029</p>
    <p>JavaScript 字符串允许直接输入字符，以及输入字符的转义形式。举例来说，“中”的 Unicode 码点是 U+4e2d，你可以直接在字符串里面输入这个汉字，也可以输入它的转义形式\u4e2d，两者是等价的。</p>
    <p>'中' === '\u4e2d' // true</p>
    <p>但是，JavaScript 规定有5个字符，不能在字符串里面直接使用，只能使用转义形式。</p>
    <p>U+005C：反斜杠（reverse solidus)</p>
    <p>U+000D：回车（carriage return）</p>
    <p>U+2028：行分隔符（line separator）</p>
    <p>U+2029：段分隔符（paragraph separator）</p>
    <p>U+000A：换行符（line feed）</p>
    <p>举例来说，字符串里面不能直接包含反斜杠，一定要转义写成\\或者\u005c。</p>
    <p>这个规定本身没有问题，麻烦在于 JSON 格式允许字符串里面直接使用 U+2028（行分隔符）和 U+2029（段分隔符）。这样一来，服务器输出的 JSON 被JSON.parse解析，就有可能直接报错。</p>
    <p>JSON 格式已经冻结（RFC 7159），没法修改了。为了消除这个报错，现在有一个提案，允许 JavaScript 字符串直接输入 U+2028（行分隔符）和 U+2029（段分隔符）。</p>
    <p>const PS = eval("'\u2029'");</p>
    <p>根据这个提案，上面的代码不会报错。</p>
    <p>注意，模板字符串现在就允许直接输入这两个字符。另外，正则表达式依然不允许直接输入这两个字符，这是没有问题的，因为 JSON 本来就不允许直接包含正则表达式。</p>
    <p>函数的部分执行</p>
    <p>语法</p>
    <p>多参数的函数有时需要绑定其中的一个或多个参数，然后返回一个新函数。</p>
    <p>function add(x, y) { return x + y; }</p>
    <p>function add7(x) { return x + 7; }</p>
    <p>上面代码中，add7函数其实是add函数的一个特殊版本，通过将一个参数绑定为7，就可以从add得到add7。</p>
    <p>// bind 方法</p>
    <p>const add7 = add.bind(null, 7);</p>
    <p>// 箭头函数</p>
    <p>const add7 = x => add(x, 7);</p>
    <p>上面两种写法都有些冗余。其中，bind方法的局限更加明显，它必须提供this，并且只能从前到后一个个绑定参数，无法只绑定非头部的参数。</p>
    <p>现在有一个提案，使得绑定参数并返回一个新函数更加容易。这叫做函数的部分执行（partial application）。</p>
    <p>const add = (x, y) => x + y;</p>
    <p>const addOne = add(1, ?);</p>
    <p>const maxGreaterThanZero = Math.max(0, ...);</p>
    <p>根据新提案，?是单个参数的占位符，...是多个参数的占位符。以下的形式都属于函数的部分执行。</p>
    <p>f(x, ?)</p>
    <p>f(x, ...)</p>
    <p>f(?, x)</p>
    <p>f(..., x)</p>
    <p>f(?, x, ?)</p>
    <p>f(..., x, ...)</p>
    <p>?和...只能出现在函数的调用之中，并且会返回一个新函数。</p>
    <p>const g = f(?, 1, ...);</p>
    <p>// 等同于</p>
    <p>const g = (x, ...y) => f(x, 1, ...y);</p>
    <p>函数的部分执行，也可以用于对象的方法。</p>
    <p>let obj = {</p>
    <p>    f(x, y) { return x + y; },</p>
    <p>};</p>
    <p>const g = obj.f(?, 3);</p>
    <p>g(1) // 4</p>
    <p>注意点</p>
    <p>函数的部分执行有一些特别注意的地方。</p>
    <p>（1）函数的部分执行是基于原函数的。如果原函数发生变化，部分执行生成的新函数也会立即反映这种变化。</p>
    <p>let f = (x, y) => x + y;</p>
    <p>const g = f(?, 3);</p>
    <p>g(1); // 4</p>
    <p>// 替换函数 f</p>
    <p>f = (x, y) => x * y;</p>
    <p>g(1); // 3</p>
    <p>上面代码中，定义了函数的部分执行以后，更换原函数会立即影响到新函数。</p>
    <p>（2）如果预先提供的那个值是一个表达式，那么这个表达式并不会在定义时求值，而是在每次调用时求值。</p>
    <p>let a = 3;</p>
    <p>const f = (x, y) => x + y;</p>
    <p>const g = f(?, a);</p>
    <p>g(1); // 4</p>
    <p>// 改变 a 的值</p>
    <p>a = 10;</p>
    <p>g(1); // 11</p>
    <p>上面代码中，预先提供的参数是变量a，那么每次调用函数g的时候，才会对a进行求值。</p>
    <p>（3）如果新函数的参数多于占位符的数量，那么多余的参数将被忽略。</p>
    <p>const f = (x, ...y) => [x, ...y];</p>
    <p>const g = f(?, 1);</p>
    <p>g(2, 3, 4); // [2, 1]</p>
    <p>上面代码中，函数g只有一个占位符，也就意味着它只能接受一个参数，多余的参数都会被忽略。</p>
    <p>写成下面这样，多余的参数就没有问题。</p>
    <p>const f = (x, ...y) => [x, ...y];</p>
    <p>const g = f(?, 1, ...);</p>
    <p>g(2, 3, 4); // [2, 1, 3, 4];</p>
    <p>（4）...只会被采集一次，如果函数的部分执行使用了多个...，那么每个...的值都将相同。</p>
    <p>const f = (...x) => x;</p>
    <p>const g = f(..., 9, ...);</p>
    <p>g(1, 2, 3); // [1, 2, 3, 9, 1, 2, 3]</p>
    <p>上面代码中，g定义了两个...占位符，真正执行的时候，它们的值是一样的。</p>
    <p>管道运算符</p>
    <p>Unix 操作系统有一个管道机制（pipeline），可以把前一个操作的值传给后一个操作。这个机制非常有用，使得简单的操作可以组合成为复杂的操作。许多语言都有管道的实现，现在有一个提案，让 JavaScript 也拥有管道机制。</p>
    <p>JavaScript 的管道是一个运算符，写作|>。它的左边是一个表达式，右边是一个函数。管道运算符把左边表达式的值，传入右边的函数进行求值。</p>
    <p>x |> f</p>
    <p>// 等同于</p>
    <p>f(x)</p>
    <p>管道运算符最大的好处，就是可以把嵌套的函数，写成从左到右的链式表达式。</p>
    <p>function doubleSay (str) {</p>
    <p>    return str + ", " + str;</p>
    <p>}</p>
    <p>function capitalize (str) {</p>
    <p>    return str[0].toUpperCase() + str.substring(1);</p>
    <p>}</p>
    <p>function exclaim (str) {</p>
    <p>    return str + '!';</p>
    <p>}</p>
    <p>上面是三个简单的函数。如果要嵌套执行，传统的写法和管道的写法分别如下。</p>
    <p>// 传统的写法</p>
    <p>exclaim(capitalize(doubleSay('hello')))</p>
    <p>// "Hello, hello!"</p>
    <p>// 管道的写法</p>
    <p>'hello'</p>
    <p>    |> doubleSay</p>
    <p>    |> capitalize</p>
    <p>    |> exclaim</p>
    <p>// "Hello, hello!"</p>
    <p>管道运算符只能传递一个值，这意味着它右边的函数必须是一个单参数函数。如果是多参数函数，就必须进行柯里化，改成单参数的版本。</p>
    <p>function double (x) { return x + x; }</p>
    <p>function add (x, y) { return x + y; }</p>
    <p>let person = { score: 25 };</p>
    <p>person.score</p>
    <p>    |> double</p>
    <p>    |> (_ => add(7, _))</p>
    <p>// 57</p>
    <p>上面代码中，add函数需要两个参数。但是，管道运算符只能传入一个值，因此需要事先提供另一个参数，并将其改成单参数的箭头函数_ => add(7, _)。这个函数里面的下划线并没有特别的含义，可以用其他符号代替，使用下划线只是因为，它能够形象地表示这里是占位符。</p>
    <p>管道运算符对于await函数也适用。</p>
    <p>x |> await f</p>
    <p>// 等同于</p>
    <p>await f(x)</p>
    <p>const userAge = userId |> await fetchUserById |> getAgeFromUser;</p>
    <p>// 等同于</p>
    <p>const userAge = getAgeFromUser(await fetchUserById(userId));</p>
    <p>数值分隔符</p>
    <p>欧美语言中，较长的数值允许每三位添加一个分隔符（通常是一个逗号），增加数值的可读性。比如，1000可以写作1,000。</p>
    <p>现在有一个提案，允许 JavaScript 的数值使用下划线（_）作为分隔符。</p>
    <p>let budget = 1_000_000_000_000;</p>
    <p>budget === 10 ** 12 // true</p>
    <p>JavaScript 的数值分隔符没有指定间隔的位数，也就是说，可以每三位添加一个分隔符，也可以每一位、每两位、每四位添加一个。</p>
    <p>123_00 === 12_300 // true</p>
    <p>12345_00 === 123_4500 // true</p>
    <p>12345_00 === 1_234_500 // true</p>
    <p>小数和科学计数法也可以使用数值分隔符。</p>
    <p>// 小数</p>
    <p>0.000_001</p>
    <p>// 科学计数法</p>
    <p>1e10_000</p>
    <p>数值分隔符有几个使用注意点。</p>
    <p>不能在数值的最前面（leading）或最后面（trailing）。</p>
    <p>不能两个或两个以上的分隔符连在一起。</p>
    <p>小数点的前后不能有分隔符。</p>
    <p>科学计数法里面，表示指数的e或E前后不能有分隔符。</p>
    <p>下面的写法都会报错。</p>
    <p>// 全部报错</p>
    <p>3_.141</p>
    <p>3._141</p>
    <p>1_e12</p>
    <p>1e_12</p>
    <p>123__456</p>
    <p>_1464301</p>
    <p>1464301_</p>
    <p>除了十进制，其他进制的数值也可以使用分隔符。</p>
    <p>// 二进制</p>
    <p>0b1010_0001_1000_0101</p>
    <p>// 十六进制</p>
    <p>0xA0_B0_C0</p>
    <p>注意，分隔符不能紧跟着进制的前缀0b、0B、0o、0O、0x、0X。</p>
    <p>// 报错</p>
    <p>0_b111111000</p>
    <p>0b_111111000</p>
    <p>下面三个将字符串转成数值的函数，不支持数值分隔符。主要原因是提案的设计者认为，数值分隔符主要是为了编码时书写数值的方便，而不是为了处理外部输入的数据。</p>
    <p>Number()</p>
    <p>parseInt()</p>
    <p>parseFloat()</p>
    <p>Number('123_456') // NaN</p>
    <p>parseInt('123_456') // 123</p>
    <p>BigInt 数据类型</p>
    <p>简介</p>
    <p>JavaScript 所有数字都保存成 64 位浮点数，这给数值的表示带来了两大限制。一是数值的精度只能到 53 个二进制位（相当于 16 个十进制位），大于这个范围的整数，JavaScript 是无法精确表示的，这使得 JavaScript 不适合进行科学和金融方面的精确计算。二是大于或等于2的1024次方的数值，JavaScript 无法表示，会返回Infinity。</p>
    <p>// 超过 53 个二进制位的数值，无法保持精度</p>
    <p>Math.pow(2, 53) === Math.pow(2, 53) + 1 // true</p>
    <p>// 超过 2 的 1024 次方的数值，无法表示</p>
    <p>Math.pow(2, 1024) // Infinity</p>
    <p>现在有一个提案，引入了一种新的数据类型 BigInt（大整数），来解决这个问题。BigInt 只用来表示整数，没有位数的限制，任何位数的整数都可以精确表示。</p>
    <p>const a = 2172141653n;</p>
    <p>const b = 15346349309n;</p>
    <p>// BigInt 可以保持精度</p>
    <p>a * b // 33334444555566667777n</p>
    <p>// 普通整数无法保持精度</p>
    <p>Number(a) * Number(b) // 33334444555566670000</p>
    <p>为了与 Number 类型区别，BigInt 类型的数据必须添加后缀n。</p>
    <p>1234 // 普通整数</p>
    <p>1234n // BigInt</p>
    <p>// BigInt 的运算</p>
    <p>1n + 2n // 3n</p>
    <p>BigInt 同样可以使用各种进制表示，都要加上后缀n。</p>
    <p>0b1101n // 二进制</p>
    <p>0o777n // 八进制</p>
    <p>0xFFn // 十六进制</p>
    <p>BigInt 与普通整数是两种值，它们之间并不相等。</p>
    <p>42n === 42 // false</p>
    <p>typeof运算符对于 BigInt 类型的数据返回bigint。</p>
    <p>typeof 123n // 'bigint'</p>
    <p>BigInt 可以使用负号（-），但是不能使用正号（+），因为会与 asm.js 冲突。</p>
    <p>-42n // 正确</p>
    <p>+42n // 报错</p>
    <p>BigInt 对象</p>
    <p>JavaScript 原生提供BigInt对象，可以用作构造函数生成 BigInt 类型的数值。转换规则基本与Number()一致，将其他类型的值转为 BigInt。</p>
    <p>BigInt(123) // 123n</p>
    <p>BigInt('123') // 123n</p>
    <p>BigInt(false) // 0n</p>
    <p>BigInt(true) // 1n</p>
    <p>BigInt()构造函数必须有参数，而且参数必须可以正常转为数值，下面的用法都会报错。</p>
    <p>new BigInt() // TypeError</p>
    <p>BigInt(undefined) //TypeError</p>
    <p>BigInt(null) // TypeError</p>
    <p>BigInt('123n') // SyntaxError</p>
    <p>BigInt('abc') // SyntaxError</p>
    <p>上面代码中，尤其值得注意字符串123n无法解析成 Number 类型，所以会报错。</p>
    <p>参数如果是小数，也会报错。</p>
    <p>BigInt(1.5) // RangeError</p>
    <p>BigInt('1.5') // SyntaxError</p>
    <p>BigInt 对象继承了 Object 提供的实例方法。</p>
    <p>BigInt.prototype.toLocaleString()</p>
    <p>BigInt.prototype.toString()</p>
    <p>BigInt.prototype.valueOf()</p>
    <p>此外，还提供了三个静态方法。</p>
    <p>BigInt.asUintN(width, BigInt)： 给定的 BigInt 转为 0 到 2width - 1 之间对应的值。</p>
    <p>BigInt.asIntN(width, BigInt)：给定的 BigInt 转为 -2width - 1 到 2width - 1 - 1 之间对应的值。</p>
    <p>BigInt.parseInt(string[, radix])：近似于Number.parseInt()，将一个字符串转换成指定进制的 BigInt。</p>
    <p>const max = 2n ** (64n - 1n) - 1n;</p>
    <p>BigInt.asIntN(64, max)</p>
    <p>// 9223372036854775807n</p>
    <p>BigInt.asIntN(64, max + 1n)</p>
    <p>// -9223372036854775808n</p>
    <p>BigInt.asUintN(64, max + 1n)</p>
    <p>// 9223372036854775808n</p>
    <p>上面代码中，max是64位带符号的 BigInt 所能表示的最大值。如果对这个值加1n，BigInt.asIntN()将会返回一个负值，因为这时新增的一位将被解释为符号位。而BigInt.asUintN()方法由于不存在符号位，所以可以正确返回结果。</p>
    <p>如果BigInt.asIntN()和BigInt.asUintN()指定的位数，小于数值本身的位数，那么头部的位将被舍弃。</p>
    <p>const max = 2n ** (64n - 1n) - 1n;</p>
    <p>BigInt.asIntN(32, max) // -1n</p>
    <p>BigInt.asUintN(32, max) // 4294967295n</p>
    <p>上面代码中，max是一个64位的 BigInt，如果转为32位，前面的32位都会被舍弃。</p>
    <p>下面是BigInt.parseInt()的例子。</p>
    <p>// Number.parseInt() 与 BigInt.parseInt() 的对比</p>
    <p>Number.parseInt('9007199254740993', 10)</p>
    <p>// 9007199254740992</p>
    <p>BigInt.parseInt('9007199254740993', 10)</p>
    <p>// 9007199254740993n</p>
    <p>上面代码中，由于有效数字超出了最大限度，Number.parseInt方法返回的结果是不精确的，而BigInt.parseInt方法正确返回了对应的 BigInt。</p>
    <p>对于二进制数组，BigInt 新增了两个类型BigUint64Array和BigInt64Array，这两种数据类型返回的都是64位 BigInt。DataView对象的实例方法DataView.prototype.getBigInt64()和DataView.prototype.getBigUint64()，返回的也是 BigInt。</p>
    <p>转换规则</p>
    <p>可以使用Boolean()、Number()和String()这三个方法，将 BigInt 可以转为布尔值、数值和字符串类型。</p>
    <p>Boolean(0n) // false</p>
    <p>Boolean(1n) // true</p>
    <p>Number(1n)    // 1</p>
    <p>String(1n)    // "1"</p>
    <p>上面代码中，注意最后一个例子，转为字符串时后缀n会消失。</p>
    <p>另外，取反运算符（!）也可以将 BigInt 转为布尔值。</p>
    <p>!0n // true</p>
    <p>!1n // false</p>
    <p>数学运算</p>
    <p>数学运算方面，BigInt 类型的+、-、*和**这四个二元运算符，与 Number 类型的行为一致。除法运算/会舍去小数部分，返回一个整数。</p>
    <p>9n / 5n</p>
    <p>// 1n</p>
    <p>几乎所有的数值运算符都可以用在 BigInt，但是有两个例外。</p>
    <p>不带符号的右移位运算符>>></p>
    <p>一元的求正运算符+</p>
    <p>上面两个运算符用在 BigInt 会报错。前者是因为>>>运算符是不带符号的，但是 BigInt 总是带有符号的，导致该运算无意义，完全等同于右移运算符>>。后者是因为一元运算符+在 asm.js 里面总是返回 Number 类型，为了不破坏 asm.js 就规定+1n会报错。</p>
    <p>BigInt 不能与普通数值进行混合运算。</p>
    <p>1n + 1.3 // 报错</p>
    <p>上面代码报错是因为无论返回的是 BigInt 或 Number，都会导致丢失精度信息。比如(2n**53n + 1n) + 0.5这个表达式，如果返回 BigInt 类型，0.5这个小数部分会丢失；如果返回 Number 类型，有效精度只能保持 53 位，导致精度下降。</p>
    <p>同样的原因，如果一个标准库函数的参数预期是 Number 类型，但是得到的是一个 BigInt，就会报错。</p>
    <p>// 错误的写法</p>
    <p>Math.sqrt(4n) // 报错</p>
    <p>// 正确的写法</p>
    <p>Math.sqrt(Number(4n)) // 2</p>
    <p>上面代码中，Math.sqrt的参数预期是 Number 类型，如果是 BigInt 就会报错，必须先用Number方法转一下类型，才能进行计算。</p>
    <p>asm.js 里面，|0跟在一个数值的后面会返回一个32位整数。根据不能与 Number 类型混合运算的规则，BigInt 如果与|0进行运算会报错。</p>
    <p>1n | 0 // 报错</p>
    <p>其他运算</p>
    <p>BigInt 对应的布尔值，与 Number 类型一致，即0n会转为false，其他值转为true。</p>
    <p>if (0n) {</p>
    <p>    console.log('if');</p>
    <p>} else {</p>
    <p>    console.log('else');</p>
    <p>}</p>
    <p>// else</p>
    <p>上面代码中，0n对应false，所以会进入else子句。</p>
    <p>比较运算符（比如>）和相等运算符（==）允许 BigInt 与其他类型的值混合计算，因为这样做不会损失精度。</p>
    <p>0n < 1 // true</p>
    <p>0n < true // true</p>
    <p>0n == 0 // true</p>
    <p>0n == false // true</p>
    <p>0n === 0 // false</p>
    <p>BigInt 与字符串混合运算时，会先转为字符串，再进行运算。</p>
    <p>'' + 123n // "123"</p>
    <p>Math.signbit()</p>
    <p>Math.sign()用来判断一个值的正负，但是如果参数是-0，它会返回-0。</p>
    <p>Math.sign(-0) // -0</p>
    <p>这导致对于判断符号位的正负，Math.sign()不是很有用。JavaScript 内部使用 64 位浮点数（国际标准 IEEE 754）表示数值，IEEE 754 规定第一位是符号位，0表示正数，1表示负数。所以会有两种零，+0是符号位为0时的零值，-0是符号位为1时的零值。实际编程中，判断一个值是+0还是-0非常麻烦，因为它们是相等的。</p>
    <p>+0 === -0 // true</p>
    <p>目前，有一个提案，引入了Math.signbit()方法判断一个数的符号位是否设置了。</p>
    <p>Math.signbit(2) //false</p>
    <p>Math.signbit(-2) //true</p>
    <p>Math.signbit(0) //false</p>
    <p>Math.signbit(-0) //true</p>
    <p>可以看到，该方法正确返回了-0的符号位是设置了的。</p>
    <p>该方法的算法如下。</p>
    <p>如果参数是NaN，返回false</p>
    <p>如果参数是-0，返回true</p>
    <p>如果参数是负值，返回true</p>
    <p>其他情况返回false</p>
</div>
